<template>
  <div class="dhtmlx-gantt-wrap">
    <el-card shadow="hover" :body-style="{ paddingBottom: '6px' }">
      <div class="gantt-toolbar">
        <div class="toolbar-actions">
          <span class="legend-chip" title="实际"><i class="legend-color" style="background:#409EFF"></i> 实际</span>
          <span class="legend-chip" title="延期"><i class="legend-color" style="background:#e35d6a"></i> 延期</span>
          <span class="legend-chip" title="计划基线"><i class="legend-color" style="background:#f59e0b"></i> 计划</span>
          <span class="toolbar-label">行显示</span>
          <el-select v-model="state.segmentCombine" size="small" style="width: 140px" @change="reload">
            <el-option label="标准" :value="'none'" />
            <el-option
              v-for="opt in combineOptions"
              :key="opt.value"
              :label="opt.label"
              :value="opt.value"
            />
          </el-select>
          <span class="toolbar-label">连线</span>
          <el-switch v-model="state.showLinks" @change="applyLinkToggle" />
          <span class="toolbar-label">刻度</span>
          <el-select v-model="state.scale" size="small" style="width: 120px" @change="applyScale">
            <el-option label="日" value="day" />
            <el-option label="周" value="week" />
            <el-option label="月" value="month" />
          </el-select>
          <span class="toolbar-label">展开</span>
          <el-select v-model="state.expandTarget" size="small" style="width: 120px" :disabled="state.loading">
            <el-option :label="'展开到 L1'" :value="1" />
            <el-option v-for="lvl in dimsCount" :key="lvl" :label="'展开到 L' + lvl" :value="lvl" v-show="lvl>1" />
            <el-option label="展开全部" :value="'all'" />
          </el-select>
          <el-button size="small" type="success" :loading="state.loading" @click="reload" :disabled="state.loading">展开</el-button>
          <el-button size="small" @click="collapseAll" :disabled="state.loading">收起全部</el-button>
          <!-- 隐藏的高级控制：分支排序/自由排序/连线/基线/定位始终启用，但不显示在 UI -->
          <span class="toolbar-label">高度</span>
          <el-slider v-model="state.hostHeight" :min="360" :max="1000" :step="20" style="width: 140px" @change="resizeHost" />
          <el-button size="small" :loading="state.loading" @click="reload">刷新</el-button>
        </div>
      </div>
    </el-card>
    <div ref="hostRef" class="dhtmlx-gantt-host" :style="{ height: state.hostHeight + 'px' }"></div>
  </div>
</template>

<script lang="ts" setup>
import { nextTick, onBeforeUnmount, onMounted, reactive, ref, watch, computed } from 'vue';
// 本地依赖：已通过 pnpm 安装 dhtmlx-gantt
import 'dhtmlx-gantt';
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
import { usePlanViewApi } from '/@/api/plans/planView';

const props = defineProps<{
  filters: any;
  dims: string[];
  timeField: 'both' | 'plan' | 'actual';
  timeMatch: 'intersects' | 'startWithin' | 'endWithin';
}>();

const planViewApi = usePlanViewApi();
const hostRef = ref<HTMLDivElement | null>(null);
const state = reactive({
  loading: false,
  initialized: false,
  scale: 'week' as 'day' | 'week' | 'month',
  pageIndex: 1,
  pageSize: 500,
  expandTarget: 'all' as number | 'all',
  showBaseline: true, // 始终启用
  hostHeight: 720,
  orderBranch: true,      // 始终启用
  orderBranchFree: true,  // 始终启用
  showLinks: true,        // 默认显示连线
  segmentCombine: 'none' as 'none' | number,  // 合并层：'none' 或 目标层索引(1..dims.length-1)
  scrollPercent: 0,       // 横向时间位置(0-100)
});

const dimsCount = computed(() => Math.max(Array.isArray(props.dims) ? props.dims.length : 1, 1));
const dimLabelMap: Record<string, string> = {
  category: '分类',
  ship: '船号',
  section: '分段',
  region: '区域',
};
const combineOptions = computed(() => {
  const list: Array<{ value: number; label: string }> = [];
  const arr = Array.isArray(props.dims) ? props.dims : [];
  for (let i = 1; i < arr.length; i++) {
    const key = arr[i] || `维度${i+1}`;
    const text = dimLabelMap[key] || key;
    list.push({ value: i, label: `${text}合并` });
  }
  return list;
});

function toDate(value?: string): Date | null {
  if (!value) return null;
  const t = Date.parse(value);
  if (Number.isNaN(t)) return null;
  return new Date(t);
}

type GNode = {
  id: string;
  label: string;
  level: number;
  keyPath?: string[];
  displayPath?: string[];
  hasChildren?: boolean;
  planStart?: string;
  planEnd?: string;
  actualStart?: string;
  actualEnd?: string;
  children?: GNode[];
  render?: string;
  splitChildren?: Array<{ id: string; label: string; keyPath?: string[]; planStart?: string; planEnd?: string; actualStart?: string; actualEnd?: string }>;
};

type Task = {
  id: string | number;
  text: string;
  start_date?: any;
  end_date?: any;
  parent?: string | number;
  open?: boolean;
  type?: string; // 'project'/'task'
  progress?: number;
  hasChildren?: boolean;
  render?: string; // dhtmlx split support
  // 扩展：计划/实际日期，供自定义图层与列模板使用
  planStartDate?: Date | null;
  planEndDate?: Date | null;
  actualStartDate?: Date | null;
  actualEndDate?: Date | null;
  // 分段聚合行：在同一行内绘制多个子分段的时间条
  _aggRanges?: Array<{ start: Date; end: Date }>;
  // 标识该任务是split合并行中的一个小段（用于强制显示标签）
  _isSplitPart?: boolean;
};

const loadedNodeKeys = new Set<string>(); // 已加载过子级的节点（避免重复懒加载）

function flattenNodes(nodes: GNode[], parentId: string | number | null, acc: Task[] = []): Task[] {
  nodes.forEach(n => {
    // 后端 split 模式：父节点带 render:'split' + splitChildren
    if ((n.render === 'split' || (n as any).Render === 'split') && Array.isArray((n as any).splitChildren)) {
      // 使用传入的 parentId 作为父级，将当前 split 节点渲染为一行（父任务），子段作为其子任务
      const thisId = n.keyPath?.join('|') || n.id;
      const pStart = toDate(n.planStart);
      const pEnd = toDate(n.planEnd);
      const aStart = toDate(n.actualStart);
      const aEnd = toDate(n.actualEnd);
      const partsRanges: Array<{ start: Date; end: Date; text?: string }> = [];
      const parentTask: Task = {
        id: thisId,
        text: n.label || '',
        start_date: aStart || pStart || undefined,
        end_date: aEnd || pEnd || aStart || pStart || undefined,
        parent: parentId || 0,
        open: true,
        type: 'task',
        hasChildren: true,
        render: 'split',
        planStartDate: pStart,
        planEndDate: pEnd,
        actualStartDate: aStart,
        actualEndDate: aEnd,
        _aggRanges: partsRanges, // 覆盖层fallback：用于绘制多段
      };
      acc.push(parentTask);
      // 生成子段任务
      const segs = (n as any).splitChildren as Array<any>;
      segs.forEach((s, i) => {
        const ss = toDate(s.actualStart) || toDate(s.planStart);
        const ee = toDate(s.actualEnd) || toDate(s.planEnd) || ss;
        if (!ss || !ee) return;
        partsRanges.push({ start: ss, end: ee, text: s.label || '' });
        const segTask: Task = {
          id: (s.id || (thisId + '|seg' + i)),
          text: s.label || '',
          start_date: ss,
          end_date: ee,
          parent: thisId,
          open: false,
          type: 'task',
          _isSplitPart: true,
        };
        acc.push(segTask);
      });
      return;
    }
    const pStart = toDate(n.planStart);
    const pEnd = toDate(n.planEnd);
    const aStart = toDate(n.actualStart);
    const aEnd = toDate(n.actualEnd);
    const start = aStart || pStart;
    const end = aEnd || pEnd || aStart || pStart;
    const t: Task = {
      id: n.keyPath?.join('|') || n.id,
      text: n.label || (n.keyPath?.slice(-1)[0] || ''),
      start_date: start || undefined,
      end_date: end || undefined,
      parent: parentId || 0,
      open: true,
      type: n.hasChildren ? 'project' : 'task',
      hasChildren: !!n.hasChildren,
      planStartDate: pStart,
      planEndDate: pEnd,
      actualStartDate: aStart,
      actualEndDate: aEnd,
    };
    acc.push(t);
    if (n.children && n.children.length) {
      flattenNodes(n.children, t.id, acc);
      loadedNodeKeys.add(String(t.id)); // 初始已包含子级的节点记为已加载
    }
  });
  return acc;
}

function applyScale() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  gantt.config.date_format = '%Y-%m-%d %H:%i';
  gantt.config.min_column_width = 40;
  gantt.config.smart_rendering = true;
  // 使用新版 scales 配置，保证切换即时生效
  const dateToStrDay = gantt.date.date_to_str('%m/%d');
  const dateToStrMonth = gantt.date.date_to_str('%Y-%m');
  if (state.scale === 'day') {
    gantt.config.scales = [
      { unit: 'day', step: 1, format: '%m/%d' },
    ];
  } else if (state.scale === 'week') {
    gantt.config.scales = [
      { unit: 'month', step: 1, format: (date: Date) => dateToStrMonth(date) },
      { unit: 'week', step: 1, format: (date: Date) => {
          const end = gantt.date.add(date, 6, 'day');
          return `${dateToStrDay(date)}~${dateToStrDay(end)}`;
        }
      },
    ];
  } else {
    gantt.config.scales = [
      { unit: 'year', step: 1, format: '%Y' },
      { unit: 'month', step: 1, format: (date: Date) => dateToStrMonth(date) },
    ];
  }
  // 设置可视范围并渲染
  setVisibleRangeFromState();
  gantt.render();
  autoLocate();
}

function setVisibleRangeFromState(paddingDays = 3) {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  const s = toDate((window as any).__planview_timeStart || undefined);
  const e = toDate((window as any).__planview_timeEnd || undefined);
  if (!s || !e) return;
  const padMs = paddingDays * 24 * 3600 * 1000;
  gantt.config.start_date = new Date(s.getTime() - padMs);
  gantt.config.end_date = new Date(e.getTime() + padMs);
}

function autoLocate() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  const s = toDate((window as any).__planview_timeStart || undefined);
  const e = toDate((window as any).__planview_timeEnd || undefined);
  const today = new Date();
  if (s && e && today >= s && today <= e) gantt.showDate(today);
  else if (s) gantt.showDate(s);
}

function applyScrollPercent() {
  const gantt = (window as any).gantt;
  if (!gantt || !hostRef.value) return;
  // 使用内部滚动容器进行定位
  const area: HTMLElement | null =
    ((gantt as any).$task_data as HTMLElement) ||
    (hostRef.value.querySelector('.gantt_task') as HTMLElement) ||
    (hostRef.value.querySelector('.gantt_data_area') as HTMLElement) ||
    hostRef.value;
  if (!area) return;
  const max = Math.max(1, area.scrollWidth - area.clientWidth);
  area.scrollLeft = Math.round((state.scrollPercent / 100) * max);
  }
async function reload() {
  const gantt = (window as any).gantt;
  state.loading = true;
  try {
    const res = await planViewApi.getGantt({
      filters: props.filters,
      timeField: props.timeField,
      timeMatch: props.timeMatch,
      dims: props.dims,
      pageIndex: state.pageIndex,
      pageSize: state.pageSize,
      expandDepth: state.expandTarget === 'all' ? undefined : Number(state.expandTarget || 1),
      expandAll: state.expandTarget === 'all',
      lazy: false,
      combineDim: state.segmentCombine === 'none' ? undefined : (props.dims?.[Number(state.segmentCombine)] || undefined),
      combineMode: state.segmentCombine === 'none' ? undefined : 'split',
    } as any);
    const data = res.data?.result || res.data || {};
    const nodes = (data.nodes || []) as GNode[];
    let tasks = flattenNodes(nodes, 0, []);
    // 前端fallback：若选择了合并层，但后端未返回 render:'split'，则在前端构建 split 结构
    if (state.segmentCombine !== 'none' && !nodes.some((n:any)=> n.render === 'split' || (n as any).Render === 'split')) {
      tasks = buildClientSplitByLevel(tasks, Number(state.segmentCombine || 1));
    }
    // 可选：生成父子连线（示意），仅在打开 showLinks 时
    const links: any[] = [];
    if (state.showLinks) {
      const map = new Map<string|number, Task>();
      tasks.forEach(t => map.set(t.id, t));
      const LT = (window as any).gantt.config.links;
      tasks.forEach(t => {
        if (t.parent && map.has(t.parent)) {
          links.push({
            id: 'L_' + t.id,
            source: t.parent,
            target: t.id,
            type: LT.start_to_start, // 父子起点连线
          });
        }
      });
    }
    if (gantt) {
      // 依据任务实际范围调整可视时间范围（避免右侧看不到）
      const taskMin = getTasksMinStart(tasks);
      const taskMax = getTasksMaxEnd(tasks);
      const s = toDate(data.timeStart) || taskMin;
      const e = toDate(data.timeEnd) || taskMax;
      (window as any).__planview_timeStart = s ? s.toISOString() : '';
      (window as any).__planview_timeEnd = e ? e.toISOString() : '';
      // 先设置刻度与范围，再 parse 数据
      applyScale();
      gantt.clearAll();
      gantt.parse({ data: tasks, links });
      applyLayers();
      if (s) gantt.showDate(s);
    }
  } finally {
    state.loading = false;
  }
}

function getTasksMinStart(tasks: Task[]): Date | null {
  let min: number | null = null;
  for (const t of tasks) {
    const cand = (t.actualStartDate as Date) || (t.start_date as Date);
    if (cand) {
      const ms = cand.getTime();
      if (min == null || ms < min) min = ms;
    }
    if (Array.isArray(t._aggRanges)) {
      for (const r of t._aggRanges) {
        const ms = r.start.getTime();
        if (min == null || ms < min) min = ms;
      }
    }
  }
  return min != null ? new Date(min) : null;
}
function getTasksMaxEnd(tasks: Task[]): Date | null {
  let max: number | null = null;
  for (const t of tasks) {
    const cand = (t.actualEndDate as Date) || (t.end_date as Date) || (t.actualStartDate as Date) || (t.start_date as Date);
    if (cand) {
      const ms = cand.getTime();
      if (max == null || ms > max) max = ms;
    }
    if (Array.isArray(t._aggRanges)) {
      for (const r of t._aggRanges) {
        const ms = r.end.getTime();
        if (max == null || ms > max) max = ms;
      }
    }
  }
  return max != null ? new Date(max) : null;
}

function idToKeyPath(id: string | number): string[] {
  return String(id).split('|').filter(Boolean);
}

// 生成“分段聚合行”：船号(Hxxxx)下一行新增“分段”，在该行内绘制所有分段的实际(或计划)时间段
function getLevelById(id: string | number): number {
  const kp = String(id).split('|').filter(Boolean);
  return Math.max(0, kp.length - 1);
}

// 前端构建 split 结构（fallback）：将目标层的所有实体转为父任务(render:'split') + 子段任务
function buildClientSplitByLevel(tasks: Task[], targetLevel: number): Task[] {
  if (!Array.isArray(tasks) || targetLevel <= 0) return tasks;
  const byParent = new Map<string|number, Task[]>();
  const parentsAtPrevLevel: Task[] = [];
  const byId = new Map<string|number, Task>();
  tasks.forEach(t => {
    byId.set(t.id, t);
    const level = getLevelById(t.id);
    if (t.parent != null) {
      const arr = byParent.get(t.parent) || [];
      arr.push(t);
      byParent.set(t.parent, arr);
    }
    if (level === targetLevel - 1) parentsAtPrevLevel.push(t);
  });
  const exclude = new Set<string|number>();
  const add: Task[] = [];
  parentsAtPrevLevel.forEach(parent => {
    const children = (byParent.get(parent.id) || []).filter(ch => getLevelById(ch.id) === targetLevel);
    if (!children.length) return;
    // father split task
    let min: number | null = null, max: number | null = null;
    const parts: Array<{ start_date: Date; end_date: Date; text?: string; color?: string }> = [];
    children.forEach((ch, i) => {
      const ss = (ch.actualStartDate as Date) || (ch.start_date as Date);
      const ee = (ch.actualEndDate as Date) || (ch.end_date as Date) || ss;
      if (!ss || !ee) return;
      const sMs = ss.getTime(); const eMs = ee.getTime();
      min = (min == null || sMs < min) ? sMs : min;
      max = (max == null || eMs > max) ? eMs : max;
      parts.push({ start_date: ss, end_date: ee, text: ch.text || '' });
      // exclude original child and its descendants
      exclude.add(ch.id);
      tasks.forEach(t => {
        if (String(t.id).startsWith(String(ch.id) + '|')) exclude.add(t.id);
      });
    });
    if (min == null || max == null || parts.length === 0) return;
    const dimKey = (props.dims && props.dims[targetLevel]) || `维度${targetLevel+1}`;
    const dimText = (dimLabelMap as any)[dimKey] || dimKey;
    // 仅添加父任务（render: 'split'），实际小段由覆盖层绘制，确保始终一行显示多段
    add.push({
      id: String(parent.id) + `|__split_${targetLevel}`,
      text: dimText,
      start_date: new Date(min),
      end_date: new Date(max),
      parent: parent.id,
      open: true,
      type: 'task',
      render: 'split',
      hasChildren: true,
      _aggRanges: parts.map(p => ({ start: p.start_date, end: p.end_date, text: p.text })),
    } as any);
  });
  if (!add.length) return tasks;
  const kept = tasks.filter(t => !exclude.has(t.id));
  kept.push(...add);
  return kept;
}

// 构建“按层合并”的行：将目标层的所有实体在同一行内分开显示多个区间
// targetLevel: 维度索引（0-based），例如 dims=['category','ship','section']，合并 'ship' 则 target=1，'section' 则 target=2
function buildCombinedByLevel(tasks: Task[], targetLevel: number): Task[] {
  if (!Array.isArray(tasks) || targetLevel <= 0) return tasks;
  const byParent = new Map<string|number, Task[]>();
  const byId = new Map<string|number, Task>();
  const parentsAtPrevLevel: Task[] = [];
  tasks.forEach(t => {
    byId.set(t.id, t);
    const level = getLevelById(t.id);
    if (t.parent != null) {
      const arr = byParent.get(t.parent) || [];
      arr.push(t);
      byParent.set(t.parent, arr);
    }
    if (level === targetLevel - 1) parentsAtPrevLevel.push(t);
  });

  // 构建需要排除的目标层任务及其后代
  const exclude = new Set<string|number>();
  const combinedRows: Task[] = [];
  for (const parent of parentsAtPrevLevel) {
    const children = (byParent.get(parent.id) || []).filter(ch => getLevelById(ch.id) === targetLevel);
    if (!children.length) continue;
    // 收集每个子实体的区间
    const ranges = children.map(ch => {
      const s = (ch.actualStartDate as Date) || (ch.start_date as Date);
      const e = (ch.actualEndDate as Date) || (ch.end_date as Date) || s;
      return s && e ? { start: s as Date, end: e as Date, label: ch.text } : null;
    }).filter(Boolean) as Array<{start: Date; end: Date; label: string}>;
    if (!ranges.length) continue;
    const minS = new Date(Math.min(...ranges.map(r => r.start.getTime())));
    const maxE = new Date(Math.max(...ranges.map(r => r.end.getTime())));
    const dimKey = (props.dims && props.dims[targetLevel]) || `维度${targetLevel+1}`;
    const dimText = dimLabelMap[dimKey] || dimKey;
    const combined: Task = {
      id: String(parent.id) + `|__combine_${targetLevel}`,
      text: dimText,
      parent: parent.id,
      start_date: minS,
      end_date: maxE,
      type: 'task',
      open: true,
      _aggRanges: ranges.map(r => ({ start: r.start, end: r.end })),
    };
    combinedRows.push(combined);
    // 将目标层任务及其后代排除
    for (const ch of children) {
      exclude.add(ch.id);
      // 排除后代
      tasks.forEach(t => {
        if (String(t.id) === String(ch.id)) return;
        if (String(t.id).startsWith(String(ch.id) + '|')) exclude.add(t.id);
      });
    }
  }
  if (!combinedRows.length) return tasks;
  // 过滤输出：保留非排除任务 + 插入合并行
  const kept = tasks.filter(t => !exclude.has(t.id));
  kept.push(...combinedRows);
  return kept;
}

async function ensureChildrenLoaded(parentId: string | number) {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  const pid = String(parentId);
  if (loadedNodeKeys.has(pid)) return;
  // 若已存在可见子级，也视为已加载
  let hasChild = false;
  gantt.eachTask((t: any) => {
    if (String(t.parent) === pid) hasChild = true;
  }, parentId);
  if (hasChild) {
    loadedNodeKeys.add(pid);
    return;
  }
  // 懒加载后端
  try {
    state.loading = true;
    const res = await planViewApi.getGantt({
      filters: props.filters,
      timeField: props.timeField,
      timeMatch: props.timeMatch,
      dims: props.dims,
      parentKeyPath: idToKeyPath(parentId),
      pageIndex: 1,
      pageSize: 1000,
      lazy: true,
    } as any);
    const data = res.data?.result || res.data || {};
    const nodes = (data.nodes || []) as GNode[];
    if (!nodes.length) {
      loadedNodeKeys.add(pid);
      return;
    }
    const tasks = flattenNodes(nodes, parentId, []);
    tasks.forEach(t => {
      // 避免重复添加
      if (!gantt.isTaskExists(t.id)) {
        gantt.addTask(t, parentId);
      }
    });
    loadedNodeKeys.add(pid);
    gantt.render();
  } finally {
    state.loading = false;
  }
}

function collapseAll() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  gantt.eachTask((t: any) => { t.open = false; });
  gantt.render();
}

function locateToday() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  gantt.showDate(new Date());
}
function locateStart() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  // 尝试使用当前时间范围的开始
  const s = toDate((window as any).__planview_timeStart || undefined);
  if (s) return gantt.showDate(s);
  // 回退到最早开始值
  let min: Date | null = null;
  gantt.eachTask((t: any) => { if (t.start_date && (!min || t.start_date < min)) min = t.start_date; });
  if (min) gantt.showDate(min);
}
function locateEnd() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  const e = toDate((window as any).__planview_timeEnd || undefined);
  if (e) return gantt.showDate(e);
  let max: Date | null = null;
  gantt.eachTask((t: any) => { if (t.end_date && (!max || t.end_date > max)) max = t.end_date; });
  if (max) gantt.showDate(max);
}

let baselineLayerAdded = false;
let overlayHooksBound = false;
function applyLayers() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  const hasAddTaskLayer = typeof gantt.addTaskLayer === 'function';
  if (hasAddTaskLayer) {
    // 使用自定义图层（PRO/支持版本）
    if (!baselineLayerAdded) {
      gantt.addTaskLayer(function (task: any) {
        // 分段聚合行：绘制多个小条
        if (Array.isArray(task._aggRanges) && task._aggRanges.length) {
      const wrap = document.createElement('div');
      wrap.style.position = 'absolute';
      const sizes = gantt.getTaskPosition(task);
      const laneTop = sizes.top; // 顶部对齐
      const laneH = Math.max(2, (sizes.height || 20)); // 与标准条一致高度
      task._aggRanges.forEach((r: any) => {
        if (!r.start || !r.end) return;
        const l = gantt.posFromDate(r.start);
        const rpx = gantt.posFromDate(r.end);
        const bar = document.createElement('div');
        bar.className = 'gantt-agg-bar';
        bar.style.left = Math.min(l, rpx) + 'px';
        bar.style.top = laneTop + 'px';
        bar.style.width = Math.max(2, Math.abs(rpx - l)) + 'px';
        bar.style.height = laneH + 'px';
        // 原生浏览器 tooltip
        try {
          const fmt = gantt.date.date_to_str('%Y-%m-%d');
          const name = r.text ? String(r.text) + ' ' : '';
          bar.title = `${name}${fmt(r.start)} ~ ${fmt(r.end)}`;
        } catch {}
        // label
        if (r.text) {
          const w = Math.abs(rpx - l);
          if (w > 18) {
            const label = document.createElement('div');
            label.className = 'gantt-agg-label';
            label.textContent = r.text;
            label.style.lineHeight = laneH + 'px';
            bar.appendChild(label);
          }
        }
        wrap.appendChild(bar);
      });
      return wrap;
    }
        if (!state.showBaseline) return null;
        const ps: Date | null = task.planStartDate || null;
        const pe: Date | null = task.planEndDate || null;
        if (!ps && !pe) return null;
        const start = ps || pe;
        const end = pe || ps;
        if (!start || !end) return null;
        const left = gantt.posFromDate(start);
        const right = gantt.posFromDate(end);
        const sizes = gantt.getTaskPosition(task);
        const wrap = document.createElement('div');
        wrap.className = 'gantt-plan-wrap';
        const l = Math.min(left, right);
        const w = Math.max(2, Math.abs(right - left));
        // 将计划基线放置在实际条的顶部位置，并进一步变细
        const h = 2;
        const top = sizes.top + 1; // 紧贴实际条顶部
        wrap.style.left = l + 'px';
        wrap.style.width = w + 'px';
        wrap.style.top = top + 'px';
        wrap.style.height = h + 'px';
        const bar = document.createElement('div');
        bar.className = 'gantt-bar-plan';
        wrap.appendChild(bar);
        const mL = document.createElement('i');
        mL.className = 'gantt-plan-marker left';
        wrap.appendChild(mL);
        const mR = document.createElement('i');
        mR.className = 'gantt-plan-marker right';
        wrap.appendChild(mR);
        return wrap;
      });
      baselineLayerAdded = true;
    }
    gantt.render();
  } else {
    // FREE/Fallback：用覆盖层绘制计划基线
    rebuildPlanOverlays();
    if (!overlayHooksBound) {
      const hook = () => { rebuildPlanOverlays(); rebuildAggOverlays(); };
      gantt.attachEvent('onDataRender', hook);
      gantt.attachEvent('onAfterTaskUpdate', hook);
      gantt.attachEvent('onAfterTaskAdd', hook);
      gantt.attachEvent('onAfterTaskDelete', hook);
      gantt.attachEvent('onTaskOpened', hook);
      gantt.attachEvent('onAfterTaskMove', hook);
      gantt.attachEvent('onScaleAdjusted', hook);
      overlayHooksBound = true;
    }
  }
}

function resizeHost() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  gantt.render();
}

function clearPlanOverlays() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  const area = (gantt.$task_data as HTMLElement) || document.querySelector('.gantt_task_data') as HTMLElement;
  if (!area) return;
  area.querySelectorAll('.gantt-plan-wrap').forEach(el => el.remove());
}

function rebuildPlanOverlays() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  clearPlanOverlays();
  if (!state.showBaseline) return;
  const area = (gantt.$task_data as HTMLElement) || document.querySelector('.gantt_task_data') as HTMLElement;
  if (!area) return;
  gantt.eachTask((task: any) => {
    // 分段聚合行单独处理
    if (Array.isArray(task._aggRanges) && task._aggRanges.length) return;
    const ps: Date | null = task.planStartDate || null;
    const pe: Date | null = task.planEndDate || null;
    if (!ps && !pe) return;
    const start = ps || pe;
    const end = pe || ps;
    if (!start || !end) return;
    const left = gantt.posFromDate(start);
    const right = gantt.posFromDate(end);
    const sizes = gantt.getTaskPosition(task);
    const wrap = document.createElement('div');
    wrap.className = 'gantt-plan-wrap';
    const l = Math.min(left, right);
    const w = Math.max(2, Math.abs(right - left));
    // 将计划基线放置在实际条的顶部位置，并进一步变细（fallback 覆盖层）
    const h = 2;
    const top = sizes.top + 1;
    wrap.style.left = l + 'px';
    wrap.style.width = w + 'px';
    wrap.style.top = top + 'px';
    wrap.style.height = h + 'px';
    wrap.style.position = 'absolute';
    const bar = document.createElement('div');
    bar.className = 'gantt-bar-plan';
    wrap.appendChild(bar);
    const mL = document.createElement('i');
    mL.className = 'gantt-plan-marker left';
    wrap.appendChild(mL);
    const mR = document.createElement('i');
    mR.className = 'gantt-plan-marker right';
    wrap.appendChild(mR);
    area.appendChild(wrap);
  });
}

function rebuildAggOverlays() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  const area = (gantt.$task_data as HTMLElement) || document.querySelector('.gantt_task_data') as HTMLElement;
  if (!area) return;
  // 清理旧的聚合条
  area.querySelectorAll('.gantt-agg-bar').forEach(el => el.remove());
  gantt.eachTask((task: any) => {
    if (!Array.isArray(task._aggRanges) || !task._aggRanges.length) return;
    const sizes = gantt.getTaskPosition(task);
    const laneTop = sizes.top;
    const laneH = Math.max(2, (sizes.height || 20));
    task._aggRanges.forEach((r: any) => {
      if (!r.start || !r.end) return;
      const l = gantt.posFromDate(r.start);
      const rpx = gantt.posFromDate(r.end);
      const bar = document.createElement('div');
      bar.className = 'gantt-agg-bar';
      bar.style.position = 'absolute';
      bar.style.left = Math.min(l, rpx) + 'px';
      bar.style.top = laneTop + 'px';
      bar.style.width = Math.max(2, Math.abs(rpx - l)) + 'px';
      bar.style.height = laneH + 'px';
      try {
        const fmt = gantt.date.date_to_str('%Y-%m-%d');
        const name = r.text ? String(r.text) + ' ' : '';
        bar.title = `${name}${fmt(r.start)} ~ ${fmt(r.end)}`;
      } catch {}
      if (r.text) {
        const w = Math.abs(rpx - l);
        if (w > 18) {
          const label = document.createElement('div');
          label.className = 'gantt-agg-label';
          label.textContent = r.text;
          label.style.lineHeight = laneH + 'px';
          bar.appendChild(label);
        }
      }
      area.appendChild(bar);
    });
  });
}

function applyOrdering() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  gantt.config.order_branch = !!state.orderBranch;
  gantt.config.order_branch_free = !!state.orderBranchFree;
  gantt.render();
}

function applyLinkToggle() {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  gantt.config.show_links = !!state.showLinks;
  // 重新加载以重建 links（示意父子连线）
  reload();
}

onMounted(async () => {
  const gantt = (window as any).gantt;
  if (!gantt) return;
  gantt.i18n.setLocale('cn');
  gantt.config.readonly = true;
  gantt.config.row_height = 36;
  gantt.config.task_height = 20;
  gantt.config.scroll_size = 18; // 显示底部滚动条
  // 初始分支排序配置（借鉴官方 branch ordering）
  gantt.config.order_branch = state.orderBranch;
  gantt.config.order_branch_free = state.orderBranchFree;
  // 显示连线（依赖）
  gantt.config.show_links = !!state.showLinks;
  const fmtGrid = gantt.date.date_to_str('%Y-%m-%d');
  gantt.config.columns = [
    { name: 'text', label: '任务 / 维度路径', tree: true, width: 300, resize: true },
    { name: 'start_date', label: '实际开始', align: 'center', width: 120, template: (t: any) => t.actualStartDate ? fmtGrid(t.actualStartDate) : (t.start_date ? fmtGrid(t.start_date) : '') },
    { name: 'end_date', label: '实际结束', align: 'center', width: 120, template: (t: any) => t.actualEndDate ? fmtGrid(t.actualEndDate) : (t.end_date ? fmtGrid(t.end_date) : '') },
  ];
  // 着色实际条：延期/准时 + 合并行隐藏默认主条
  gantt.templates.task_class = (start: Date, end: Date, task: any) => {
    let cls = '';
    const aEnd: Date | null = task.actualEndDate || null;
    const pEnd: Date | null = task.planEndDate || null;
    if (aEnd && pEnd && aEnd.getTime() > pEnd.getTime()) cls = 'task-delayed';
    if (Array.isArray(task._aggRanges) && task._aggRanges.length) {
      cls += (cls ? ' ' : '') + 'task-combined';
    }
    if (task.render === 'split') {
      cls += (cls ? ' ' : '') + 'task-split-parent';
    }
    if (task._isSplitPart) {
      cls += (cls ? ' ' : '') + 'split-part';
    }
    return cls;
  };
  await nextTick();
  if (hostRef.value) {
    // 显式配置包含底部横向滚动条的布局（v8）
    try {
      gantt.config.layout = {
        css: 'gantt_container',
        rows: [
          {
            cols: [
              { view: 'grid', scrollY: 'scrollVer' },
              { view: 'timeline', scrollX: 'scrollHor', scrollY: 'scrollVer' },
              { view: 'scrollbar', id: 'scrollVer' }
            ]
          },
          { view: 'scrollbar', id: 'scrollHor' }
        ]
      } as any;
    } catch {}
    gantt.init(hostRef.value);
    // 懒加载：展开节点时按需拉取子级
    gantt.attachEvent('onTaskOpened', async (id: any) => {
      await ensureChildrenLoaded(id);
    });
    // 启用 tooltip 并定义文本
    try {
      // @ts-ignore
      if (typeof (gantt as any).plugins === 'function') {
        // @ts-ignore
        (gantt as any).plugins({ tooltip: true });
      }
    } catch {}
    const fmtTip = gantt.date.date_to_str('%Y-%m-%d');
    gantt.templates.tooltip_text = function (start: Date, end: Date, task: any) {
      const name = task?.text || '';
      const range = `${fmtTip(start)} ~ ${fmtTip(end)}`;
      if (name) return `<b>${name}</b><br/>${range}`;
      return range;
    };
    applyLayers();
    state.initialized = true;
    applyScale();
    await reload();
    // 为split子段强制显示标签
    const defaultTaskText = gantt.templates.task_text;
    gantt.templates.task_text = function (start: Date, end: Date, task: any) {
      if (task && task._isSplitPart) return task.text || '';
      return typeof defaultTaskText === 'function' ? defaultTaskText(start, end, task) : (task?.text || '');
    };
    // 横向滚动百分比同步 & 鼠标滚轮横移
    const area: any = (gantt as any).$task_data || hostRef.value;
    const sync = () => {
      if (!area) return;
      const max = Math.max(1, area.scrollWidth - area.clientWidth);
      const pct = Math.max(0, Math.min(100, Math.round((area.scrollLeft / max) * 100)));
      state.scrollPercent = pct;
    };
    if (typeof gantt.attachEvent === 'function') {
      gantt.attachEvent('onGanttScroll', function (_l: number, _t: number) { sync(); });
    }
    const wheelHandler = (e: WheelEvent) => {
      if (state.scale !== 'day') return;
      if (!area) return;
      const delta = e.deltaY || e.deltaX;
      if (delta) {
        area.scrollLeft += delta;
        e.preventDefault();
        e.stopPropagation();
        sync();
      }
    };
    hostRef.value.addEventListener('wheel', wheelHandler, { passive: false });
    onBeforeUnmount(() => {
      hostRef.value?.removeEventListener('wheel', wheelHandler as any);
    });
  }
});

watch(() => [props.filters, props.dims, props.timeField, props.timeMatch], async () => {
  if (!state.initialized) return;
  await reload();
}, { deep: true });

onBeforeUnmount(() => {
  const gantt = (window as any).gantt;
  if (gantt) gantt.clearAll();
});
</script>

<style scoped>
.dhtmlx-gantt-wrap :deep(.el-card__body) { padding-top: 8px; }
.gantt-toolbar {
  display: flex; align-items: center; justify-content: flex-end; gap: 12px; flex-wrap: wrap;
}
.toolbar-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.toolbar-label { font-size: 12px; color: var(--el-text-color-secondary); }
.legend-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: #64748b; margin-right: 8px; }
.legend-color { display: inline-block; width: 10px; height: 10px; border-radius: 2px; }
.dhtmlx-gantt-host {
  margin-top: 12px;
  border: 1px solid var(--el-border-color);
  border-radius: 8px;
  background: var(--el-fill-color-blank);
  padding: 8px;
  height: 720px;
  overflow: hidden; /* 交由 dhtmlx 内部滚动处理，避免外层滚动影响横向拖拽 */
}
/* 确保 dhtmlx 内部横向滚动条可见 */
:deep(.gantt_hor_scroll) {
  height: 18px !important;
}
:deep(.gantt_layout_cell.gantt_hor_scroll) {
  height: 18px !important;
}

/* 计划轨道样式（细条） */
:deep(.gantt-bar-plan) {
  position: absolute;
  background: #f59e0b; /* 计划色：橙 */
  border-radius: 999px;
  opacity: 0.95;
  pointer-events: none;
  z-index: 2;
}
:deep(.gantt-plan-wrap) { position: absolute; }
:deep(.gantt-plan-wrap) .gantt-bar-plan {
  inset: 0;
}
:deep(.gantt-plan-marker) {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #f59e0b; /* 橙色实心圆 */
}
:deep(.gantt-plan-marker.left) { left: -4px; }
:deep(.gantt-plan-marker.right) { right: -4px; }
/* 保证计划图层覆盖在实际条之上 */
:deep(.gantt-plan-wrap),
:deep(.gantt-plan-wrap .gantt-bar-plan),
:deep(.gantt-plan-marker) {
  z-index: 50;
  pointer-events: none;
}
/* 修改连线颜色（路径与箭头） */
:deep(.gantt_link_line) { stroke: #6b7280 !important; }
:deep(.gantt_link_arrow) { fill: #6b7280 !important; stroke: #6b7280 !important; }
/* 延期任务主条着色 */
:deep(.gantt_task_line.task-delayed) {
  background-color: #e35d6a !important;
  border-color: #e35d6a !important;
}
/* 合并行隐藏默认主条（只显示我们绘制的小条集合） */
:deep(.gantt_task_line.task-combined) {
  background: transparent !important;
  border-color: transparent !important;
}
:deep(.gantt_task_content.task-combined) {
  display: none !important;
}
/* split 父任务隐藏默认条，只显示子段 */
:deep(.gantt_task_line.task-split-parent) {
  background: transparent !important;
  border-color: transparent !important;
}
:deep(.gantt_task_content.task-split-parent) {
  display: none !important;
}
/* 分段聚合行的小条样式 */
:deep(.gantt-agg-bar) {
  position: absolute;
  background: #1E3A8A; /* 深蓝色 */
  border-radius: 999px;
  z-index: 45;
  pointer-events: none;
}
:deep(.gantt-agg-label) {
  position: absolute;
  left: 4px;
  right: 4px;
  top: 0;
  bottom: 0;
  color: #ffffff;
  font-size: 12px;
  font-weight: 600;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}
/* 分段合并子条（dhtmlx 子任务）的深蓝色样式 */
:deep(.gantt_task_line.split-part) {
  background-color: #1E3A8A !important;
  border-color: #1E3A8A !important;
}
:deep(.gantt_task_content.split-part) {
  color: #ffffff !important;
  font-weight: 500;
}
</style>
